package main

import (
	"bytes"
	"fmt"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

var (
	db    *gorm.DB
	kinds []BookKind
)

const (
	// LoginOK 登录成功
	LoginOK = 0
	// LoginNONE 没有登录
	LoginNONE = 1
	// LoginUserErr 账号不存在或密码错误
	LoginUserErr = 2
	// LoginFormErr 账号密码不能为空
	LoginFormErr = 3
	// ErrUnknown 未知异常
	ErrUnknown = -1
	// RegistOk 注册成功
	RegistOk = 0
	// RegistUserAlreadyExist 账号已经存在
	RegistUserAlreadyExist = 1
	// RegistFormErr 注册表单错误
	RegistFormErr = 2
)

func init() {
	var err error
	db, err = gorm.Open("mysql", "root:Zzd123456@/business?charset=utf8&parseTime=True&loc=Local")
	if err != nil || db == nil {
		log.Fatalf("Error on initializing database connection: %s", err.Error())
	}
	db.DB().SetMaxOpenConns(1000)
	db.DB().SetMaxIdleConns(100)
	err = db.DB().Ping()
	if err != nil {
		log.Fatalf("Error on opening database connection: %s", err.Error())
	}
	// 缓存所有图书分类
	db.Find(&kinds)
}

func main() {
	defer db.Close()
	r := gin.Default()
	gin.SetMode(gin.ReleaseMode)
	r.Static("/static", "./static")
	r.GET("", index).GET("/index.html", index)
	r.GET("/register.html", register)
	r.POST("/register.html", doRegister)
	r.POST("/login.html", login)
	r.GET("//search/:type", searchType)
	r.POST("/search.html", search)
	r.GET("/book/:id", showBook)
	r.LoadHTMLGlob("pages/*")
	r.Run(":8080")
}

func index(c *gin.Context) {
	data := gin.H{"kinds": kinds, "code": 0}
	code, ok := c.GetQuery("code")
	if ok {
		pCode, _ := strconv.ParseInt(code, 10, 8)
		data["code"] = int(pCode)
	} else {
		// 检测登录状态
		if user, ok := checkLogin(c); ok {
			data["code"] = LoginOK
			data["user"] = user
		} else {
			data["code"] = LoginNONE
		}
	}
	var books []BookInfo
	db.Order(" create_time desc ").Limit(12).Find(&books)
	data["books"] = books
	c.HTML(http.StatusOK, "index.html", data)
}

// RegisterUser 注册表单结构体
type RegisterUser struct {
	Phone           string `form:"phone" json:"phone" binding:"required"`
	Password        string `form:"password" json:"password" binding:"required"`
	ConfirmPassword string `form:"confirmPassword" json:"confirmPassword" binding:"required"`
}

func register(c *gin.Context) {
	c.HTML(http.StatusOK, "register.html", nil)
}

func doRegister(c *gin.Context) {
	var rUser RegisterUser
	c.ShouldBind(&rUser)
	if len(rUser.Phone) == 0 || len(rUser.Password) == 0 || len(rUser.ConfirmPassword) == 0 || rUser.Password != rUser.ConfirmPassword {
		c.HTML(http.StatusOK, "register.html", gin.H{"code": RegistFormErr, "user": rUser})
		return
	}
	user := User{
		Phone:    rUser.Phone,
		Password: calcMd5([]byte(rUser.Password)),
	}
	errs := db.Create(&user).GetErrors()
	if len(errs) <= 0 {
		// 注册成功
		doLoginTransaction(user.Phone, c)
		c.Redirect(http.StatusMovedPermanently, "/index.html")
		return
	}
	log.Println("注册失败！", errs)
	errMsg := errs[0].Error()
	if strings.Contains(errMsg, "Error 1062") {
		c.HTML(http.StatusOK, "register.html", gin.H{"code": RegistUserAlreadyExist, "user": rUser})
		return
	}
	if strings.Contains(errMsg, "Error 1406") {
		c.HTML(http.StatusOK, "register.html", gin.H{"code": RegistFormErr, "user": rUser})
		return
	}
	c.HTML(http.StatusOK, "register.html", gin.H{"code": ErrUnknown, "errorMsg": errMsg, "user": rUser})
}

func login(c *gin.Context) {
	code := ErrUnknown
	var rUser RegisterUser
	c.ShouldBind(&rUser)
	// 检测账号密码是否为空
	if len(rUser.Phone) == 0 || len(rUser.Password) == 0 {
		code = LoginFormErr
	} else {
		// 正则检测账号是否为手机号
		match, _ := regexp.MatchString("^[1][3,4,5,7,8][0-9]{9}$", rUser.Phone)
		if !match {
			code = LoginUserErr
		} else {
			var user User
			// 数据库查找是否存在此账号
			errs := db.First(&user, rUser.Phone).GetErrors()
			if len(errs) == 0 {
				pwd := calcMd5([]byte(rUser.Password))
				if user.Password == pwd {
					code = LoginOK
					doLoginTransaction(user.Phone, c)
					c.Redirect(http.StatusMovedPermanently, "/index.html")
					return
				}
				code = LoginUserErr
			} else {
				errStr := errs[0].Error()
				if errStr == "record not found" || strings.Contains(errStr, "Error 1054") {
					code = LoginUserErr
				} else {
					//未知异常
					log.Println("登录失败！异常", errs)
				}
			}
		}
	}
	c.Redirect(http.StatusMovedPermanently, fmt.Sprintf("/index.html?code=%d", code))
}

func searchType(c *gin.Context) {
	bookType := -1
	typeStr := c.Param("type")
	if convInt, err := strconv.ParseInt(typeStr, 10, 16); err == nil {
		bookType = int(convInt)
	} else {
		c.Redirect(http.StatusMovedPermanently, "/index.html")
		return
	}
	var books []BookInfo
	db.Where(" kind_id = ? ", bookType).Order(" create_time desc ").Limit(20).Find(&books)
	c.HTML(http.StatusOK, "search.html", gin.H{"books": books})
}

func search(c *gin.Context) {
	name := c.PostForm("name")
	sDB := db
	if len(name) > 0 {
		var buffer bytes.Buffer
		buffer.WriteByte('%')
		buffer.WriteString(name)
		buffer.WriteByte('%')
		param := buffer.String()
		sDB = db.Where(" name like ? or author like ? or publisher like ?", param, param, param)
	}
	var books []BookInfo
	sDB.Order(" create_time desc ").Limit(20).Find(&books)
	c.HTML(http.StatusOK, "search.html", gin.H{"books": books, "searchName": name})
}

func showBook(c *gin.Context) {
	bookID := c.Param("id")
	bi := BookInfo{}
	if id, err := strconv.ParseInt(bookID, 10, 16); err == nil {
		db.First(&bi, int(id))
		c.HTML(http.StatusOK, "book.html", gin.H{"book": bi})
	} else {
		//错误
		c.HTML(http.StatusOK, "book.html", nil)
	}
}
